package com.example.userservice.serviceImpl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.commom.enums.RespDescEnum;
import com.example.commom.enums.StatusCodeEnum;
import com.example.commom.exception.CustomException;
import com.example.commom.global.AppVariable;
import com.example.commom.global.KafkaConstants;
import com.example.commom.global.RedisConstants;
import com.example.commom.redis.RedisUtils;
import com.example.commom.utils.JwtUtils;
import com.example.commom.utils.KafkaUtils;
import com.example.feign.entity.User;
import com.example.feign.result.AjaxResult;
import com.example.userservice.mapper.UserMapper;
import com.example.userservice.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 软工2104-李家强-2100770248
 * @since 2023-09-19
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private KafkaUtils kafkaUtils;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserService userService;

    /**
     * 通过用户名获取 用户信息
     */
    public User getUserByName(String username) {

        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getUsername, username);

        return userService.getOne(userLambdaQueryWrapper);
    }

    /**
     * 正常登录的返回结果
     */
    public HashMap<String, Object> successLogin(User userInfo) {
        // 密码正确, 生成 token
        String token = JwtUtils.generateToken(userInfo.getUserId(),userInfo.getUsername(), userInfo.getType());

        // 向 kafka 推送消息将 token 存储到 redis 中.
        kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_LOGIN_STATUS_TOPIC, KafkaConstants.KAFKA_LOGIN_STATUS_TOPIC,
                (RedisUtils.getTokenRedisKey(
                        userInfo.getType(), userInfo.getUserId()
                ) + ":true").getBytes(StandardCharsets.UTF_8));

        // 将用户信息存入 redis 中


        HashMap<String, Object> result = new HashMap<>();
        result.put("token", token);
        result.put("name", userInfo.getUsername());
        result.put("userId", userInfo.getUserId());
        result.put("email", userInfo.getEmail());
        result.put("photo", userInfo.getPhoto());
        result.put("type", userInfo.getType());

        return result;
    }


    /**
     *  退出登录
     */
    @Override
    public AjaxResult exit(String token) {

        if (!StringUtils.hasLength(token)) {
            return AjaxResult.fail(StatusCodeEnum.Redirect.getCode(), StatusCodeEnum.Redirect.getMsg());
        }

        // 拿到登录用户的id和type
        String id = JwtUtils.getUserIdByToken(token);
        if (!StringUtils.hasLength(id)) {
            return AjaxResult.fail(StatusCodeEnum.Redirect.getCode(), StatusCodeEnum.Redirect.getMsg());
        }


        User userInfo = getById(id);
        if (userInfo == null) {
            return AjaxResult.fail(StatusCodeEnum.UNAUTHORIZED.getCode(), StatusCodeEnum.UNAUTHORIZED.getMsg());
        }

        // 向 kafka 推送消息, 删除 redis 中的 token
        kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_LOGIN_STATUS_TOPIC, KafkaConstants.KAFKA_LOGIN_STATUS_TOPIC,
                (RedisUtils.getTokenRedisKey(userInfo.getType(), Long.parseLong(id)) + ":false")
                        .getBytes(StandardCharsets.UTF_8));

        return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
    }


    /**
     *  用户注册
     */
    public AjaxResult register(User user) {
        // 用户名必须唯一
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, user.getUsername());

        User userInfo = userService.getOne(wrapper);
        if (userInfo != null) {
            // 该用户名已经存在
            return AjaxResult.fail(StatusCodeEnum.FAILED.getCode(), RespDescEnum.AccountIsAlreadyExist.getValue());
        }

        // 正常注册
        boolean update = userService.saveOrUpdate(user);
        if (update) {
            return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
        } else {
            throw new CustomException(StatusCodeEnum.FAILED.getMsg(), -1);
        }
    }

    /**
     *  获取验证码
     */
     public void getCode(HttpServletRequest request, HttpServletResponse response) {
         // 通知浏览器不要缓存
         response.setHeader("pragma", "no-cache");
         response.setHeader("cache-control", "no-cache");
         response.setHeader("expires", "0");

         // 生成一个验证码图片                                                // 宽          高   几位数的验证码     干扰点
         CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(116, 36, 4, 10);

         // 拿到图片生成的验证码
         String code = circleCaptcha.getCode();
         System.out.println("code: " + code);

         // 每调用一次此方法, 就会生成新的 code, 存到 session[redis] 中
         HttpSession session = request.getSession();
         session.setAttribute(AppVariable.IMAGE_CHECK_CODE, code);

         // 拿到流对象
         ServletOutputStream outputStream = null;
         try {
             outputStream = response.getOutputStream();
         } catch (IOException e) {
             e.printStackTrace();
         }
         if (outputStream != null) {
             // 通过输出流输出给前端
             circleCaptcha.write(outputStream);
             // 关闭资源
             try {
                 outputStream.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }


    /**
     *  个人信息管理(修改普通信息)
     */
    public AjaxResult updateUserInfo(User user, String token) {

        // 判断用户 id 和登录的用户 id 一不一样
        String id = JwtUtils.getUserIdByToken(token);
        if (!StringUtils.hasLength(id)) {
            // 拿不到 id 说明 token 已过期了.
            return AjaxResult.fail(StatusCodeEnum.Redirect.getCode(), StatusCodeEnum.Redirect.getMsg());
        }

        if (!id.equals(user.getUserId() + "")) {
            // token 没过期, 但是 id 不同, 就没有权限.
            return AjaxResult.fail(StatusCodeEnum.UNAUTHORIZED.getCode(), StatusCodeEnum.UNAUTHORIZED.getMsg());
        }


        // 修改用户信息.
        try {
            kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_UPDATE_USER_INFO, KafkaConstants.KAFKA_UPDATE_USER_INFO,
                    objectMapper.writeValueAsString(user).getBytes(StandardCharsets.UTF_8));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new CustomException(e.getMessage(), StatusCodeEnum.FAILED.getCode());
        }

        if (StringUtils.hasLength(user.getUsername())) {
            // 修改用户名, 要级联修改作品的作者.

            // 查询要修改的用户名是否存在
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getUsername, user.getUsername());
            User userInfo = getOne(lambdaQueryWrapper);
            if (userInfo != null) {
                return AjaxResult.fail(StatusCodeEnum.FAILED.getCode(), RespDescEnum.AccountIsAlreadyExist.getValue());
            }

            // 如果是作者, 就把作品的作者也修改为新的用户名
            if ("1".equals(JwtUtils.getUserTypeByToken(token))) {
                // 查询旧数据
                User oldUserInfo = userService.getById(id);
                // 推送消息(旧的用户名和新的用户名), 将发布的书籍作者也改了
                kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_UPDATE_BOOK_INFO, KafkaConstants.KAFKA_UPDATE_BOOK_INFO,
                        (oldUserInfo.getUsername() + ":" + user.getUsername()).getBytes(StandardCharsets.UTF_8));
            }
        }

        return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
    }


    /**
     *  用户上传头像
     */
    public AjaxResult upLoadPhoto(MultipartFile multipartFile, User userInfo) {
        try {

            // 生成UUID作为文件名
            String fileName = UUID.randomUUID() + Objects.requireNonNull(multipartFile.getOriginalFilename()).
                    substring(multipartFile.getOriginalFilename().lastIndexOf("."));

            // 保存文件到指定位置
            multipartFile.transferTo(new File(AppVariable.USER_IMAGE_URL + fileName));

            // 更新用户的头像路径
            userInfo.setPhoto(fileName);

            boolean update = userService.saveOrUpdate(userInfo);
            if (update) {
                return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
            } else {
                throw new CustomException(StatusCodeEnum.FAILED.getMsg(), -1);
            }

        } catch (IOException e) {
            e.printStackTrace();
            return AjaxResult.fail(StatusCodeEnum.FAILED.getCode(), StatusCodeEnum.FAILED.getMsg());
        }
    }



    /**
     *  获取两个用户的共同关注
     * @param id 正在查看的用户 id
     * @param userId 登录的用户 Id
     * @return 共同关注结果集合
     */
    @Override
    public AjaxResult followCommons(String id, String userId) {

        // 从 redis 中求交集
        String idKey = RedisConstants.REDIS_FOLLOW_PRE_KRY + id;
        String userIdKey = RedisConstants.REDIS_FOLLOW_PRE_KRY + userId;

        Set<String> ids = stringRedisTemplate.opsForSet().intersect(idKey, userIdKey);

        if (ids == null || ids.size() <= 0) {
            return AjaxResult.success(StatusCodeEnum.OK.getMsg());
        }

        List<User> result = new ArrayList<>();
        for (String element : ids) {
            long userid = Long.parseLong(element);
            User user = getById(userid);
            if (user != null) {
                result.add(user);
            }
        }

        return AjaxResult.success(result);
    }

}
